package util

import (
	"math"
	"net/http"
	"time"
)

// 健康检查需求添加 -20230314 类似的可以是RedisChecker
type HttpChecker struct {
	Lb            *LoadBalance //注意，只有这样才能获取指针
	FailMax       int          //失败阈值 连续失败到达该阈值标识为down 默认是5  用于计数器机制的负载均衡-普通轮询
	SuccessCount  int          //成功阈值 连续成功到达该值标识为up 默认是2 用于计数器机制的负载均衡-普通轮询
	FailFactor    float64      //降权因子，默认是5.0 增加降权值，增加量为该服务权重的1/5  用于降权机制的负载均衡-平滑加权轮询
	RecoverFactor float64      //加权因子，默认是2.0 当健康检查成功时则减少降权值 减少量为当前服务权重的1/2  用于降权机制的负载均衡-平滑加权轮询
}

// NewSingleHttpChecker 初始化一个带有默认值的httpCheck
func NewSingleHttpChecker(lb *LoadBalance) *HttpChecker {
	//写在配置文件
	return &(HttpChecker{Lb: lb, FailMax: 5, SuccessCount: 2, FailFactor: 5.0, RecoverFactor: 2.0})
}

// 手动传参生成httpCheck
func NewHttpChecker(lb *LoadBalance, failMax int, successCount int, failFactor float64, recoverFactor float64) *HttpChecker {
	//写在配置文件
	return &(HttpChecker{Lb: lb, FailMax: failMax, SuccessCount: successCount, FailFactor: failFactor, RecoverFactor: recoverFactor})
}

// Check
// time 过期时间
func (this *HttpChecker) Check(timeout time.Duration) {
	client := http.Client{}
	//添加过期时间
	client.Timeout = timeout
	//head请求
	/*发送什么请求：
	一般做法是发送Head请求（GET/POST|PUT)
	只返回头部，不需要返回body部分，使得传输量较小，可以用来检测Url是否可用*/
	for _, server := range this.Lb.Servers {
		resp, err := client.Head(server.Host)
		if resp != nil {
			//defer延迟函数，go会把该段代码压入栈中，待该函数返回前再自动执行
			defer resp.Body.Close() //避免他继续保持连接把body也加载造成不必要的浪费。
		}
		if err != nil { //可能是宕机，但是一般要多请求几次才合理，这里先简易操作
			this.Fail(server)
			continue
		}
		//特殊处理，如处理404 502等值
		if resp.StatusCode >= 200 && resp.StatusCode <= 404 {
			this.Success(server)
		} else {
			this.Fail(server)
		}
	}
}

// Fail 计数器需求所需
func (this *HttpChecker) Fail(server *HttpServer) {
	//超过阈值设置为down且清零
	if server.FailCount >= this.FailMax {
		if server.Status == "UP" {
			this.Lb.DownServers++
		}
		server.Status = "DOWN"

	} else {
		//失败次数加一
		server.FailCount++
	}
	server.SuccessCount = 0

	//降权机制需求新增代码
	fw := int(math.Floor(float64(server.Weight)) * (1 / this.FailFactor)) //计算出每次降低的权重
	if fw == 0 {
		fw = 1
	}
	server.FailWeight += fw
	if server.FailWeight > server.Weight {
		server.FailWeight = server.Weight
	}
}

// Success 视频中成功后是FailCount--而不是直接置0，这里尝试直接设为Up 且置0，看看效果有什么不同。 补充：视频最后说这样是不合理的，会改进。
func (this *HttpChecker) Success(server *HttpServer) {
	server.SuccessCount++
	if server.SuccessCount >= this.SuccessCount {
		if server.Status == "DOWN" {
			this.Lb.DownServers--
		}
		server.Status = "UP"
		server.SuccessCount = 0
		server.FailCount = 0
	}
	sw := int(math.Floor(float64(server.Weight)) * (1 / this.RecoverFactor)) //计算每次成功增加的权重
	if sw == 0 {
		sw = 1
	}
	server.FailWeight -= sw
	//做判断不让无限减少
	if server.FailWeight < 0 {
		server.FailWeight = 0
	}

}
